package com.gitee.dufafei.plugin.core;

import com.gitee.dufafei.plugin.consts.Const;
import com.gitee.dufafei.plugin.listener.PluginListener;
import com.gitee.dufafei.plugin.vfs.FileVFS;
import com.gitee.dufafei.plugin.vfs.Folder;
import com.gitee.dufafei.plugin.vfs.FolderInterface;
import org.apache.commons.vfs2.FileObject;
import org.scannotation.AnnotationDB;

import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.*;

public abstract class BasePluginType<T> implements PluginTypeInterface {

    private String id;
    private String name;
    private String description;
    private Class<? extends Annotation> annotation;
    private List<Class<? extends T>> nativePlugins;
    private List<FolderInterface> folders;
    private List<PluginListener> listeners;

    public BasePluginType(Class<? extends Annotation> annotation) {
        this.annotation = annotation;
        this.nativePlugins = new ArrayList<>();
        this.folders = new ArrayList<>();
        this.listeners = new ArrayList<>();
    }

    public BasePluginType(Class<? extends Annotation> annotation, String id, String name, String description) {
        this(annotation);
        this.id = id;
        this.name = name;
        this.description = description;
    }

    @Override
    public String getId() { return id; }

    @Override
    public String getName() { return name; }

    @Override
    public String getDescription() { return description; }

    @Override
    public Class<? extends Annotation> getAnnotation() { return annotation; }

    public List<Class<? extends T>> getNativePlugins() { return nativePlugins; }

    public void addNativePlugin(Class<? extends T> nativePlugin) { this.nativePlugins.add(nativePlugin); }

    public List<FolderInterface> getFolders() { return folders; }

    public void addFolder(FolderInterface folder) { folders.add(folder); }

    @Override
    public List<PluginListener> getListeners() { return listeners; }

    public void addListener(PluginListener listener) { this.listeners.add(listener); }

    protected abstract String extractId(Annotation annotation);

    protected abstract String extractName(Annotation annotation);

    protected abstract String extractCategory(Annotation annotation);

    protected abstract String extractDescription(Annotation annotation);

    protected void addExpandInformation(Annotation annotation, Map<String, Object> expandInformation) {}

    public PluginInterface getPluginInterface(Class<? extends T> clazz, boolean isNative, URL[] libraries) {
        Annotation annotation = clazz.getAnnotation(this.annotation);
        String id = extractId(annotation);
        if(id == null) {
            throw new PluginException("Not a valid id specified in plugin: " + clazz.getName());
        }
        String name = extractName(annotation);
        String category = extractCategory(annotation);
        String description = extractDescription(annotation);
        Map<String, Object> expandInformation = new HashMap<>();
        addExpandInformation(annotation, expandInformation);
        PluginInterface plugin = new Plugin();
        plugin.setPluginType(getClass());
        plugin.setId(id);
        plugin.setName(name);
        plugin.setCategory(category);
        plugin.setDescription(description);
        plugin.setExpandInformation(expandInformation);
        plugin.setClassName(clazz.getName());
        plugin.setNative(isNative);
        plugin.setLibraries(libraries);
        return plugin;
    }

    @Override
    public List<PluginInterface> getPlugins() throws Exception {
        List<PluginInterface> plugins = new ArrayList<>();
        addNativePlugins(plugins);
        addThirdPartyPlugins(plugins);
        return plugins;
    }

    private void addNativePlugins(List<PluginInterface> plugins) {
        for (Class<? extends T> clazz: nativePlugins) {
            PluginInterface plugin = getPluginInterface(clazz, true, null);
            plugins.add(plugin);
        }
    }

    @SuppressWarnings("unchecked")
    private void addThirdPartyPlugins(List<PluginInterface> plugins) throws Exception {
        for (FolderInterface pluginFolder: folders) {
            if (!pluginFolder.isExists()) {
                throw new PluginException("The plug-in directory does not exist: " + pluginFolder.getPath());
            }
            FileObject[] pluginFiles = pluginFolder.findJarFiles(false);
            if (pluginFiles != null) {
                for (FileObject pluginFile: pluginFiles) {
                    AnnotationDB annotationDB = new AnnotationDB();
                    annotationDB.scanArchives(pluginFile.getURL());
                    Set<String> impls = annotationDB.getAnnotationIndex().get(annotation.getName());
                    if (impls != null && impls.size() > 0) {
                        List<FileObject> libFiles = getLibFiles(pluginFile);
                        URL[] libraries = new URL[libFiles.size() + 1];
                        for (int i = 0; i < libFiles.size(); i ++) {
                            libraries[i] = libFiles.get(i).getURL();
                        }
                        libraries[libFiles.size()] = pluginFile.getURL();
                        for (String imp: impls) {
                            ClassLoader classLoader = getClass().getClassLoader();
                            PluginURLClassLoader urlClassLoader = new PluginURLClassLoader(libraries, classLoader);
                            Class<? extends T> clazz = (Class<? extends T>) urlClassLoader.loadClass(imp);
                            if (clazz == null) {
                                throw new PluginException("Unable to load class: " + imp);
                            }
                            PluginInterface plugin = getPluginInterface(clazz, false, libraries);
                            plugins.add(plugin);
                        }
                    }
                }
            }
        }
    }

    private List<FileObject> getLibFiles(FileObject pluginFile) throws Exception {
        FileObject parentFolder = pluginFile.getParent();
        String parentFolderName = FileVFS.getFileName(parentFolder);
        String libFolderName = parentFolderName + Const.FILE_SEPARATOR + "lib";
        List<FileObject> fileObjects = new ArrayList<>();
        Folder folder = new Folder(libFolderName);
        FileObject[] jarFiles = folder.findJarFiles(true);
        if (jarFiles != null) {
            for (FileObject jarFile: jarFiles) {
                if (pluginFile.equals(jarFile)) {
                    continue;
                }
                fileObjects.add(jarFile);
            }
        }
        return fileObjects;
    }

    @Override
    public String toString() { return name + "(" + id + ")"; }
}
